﻿namespace Calculator

open System
open System.Threading
open Wpf.Mvc
open Calculator.TempConverter

type MainWindowController(view, ticks : WatchEventSource) =
    inherit SupervisingController<MainWindowEvents, MainWindowModel>(view) 

    static let Operators = 
        dict [
            "+", (+)
            "-", (-)
            "*", (*)
            "/", (/)
        ]
    
    static member Create(view) = 
        let ticks = WatchEventSource(TimeSpan.FromSeconds(1.))
        let watchController (runningTime : TimeSpan) = 
            Sync(fun(model : MainWindowModel) -> model.RunningTime <- runningTime)

        MainWindowController(view, ticks).Compose(watchController, ticks)

    override this.InitModel model = 
        model.RunningTime <- TimeSpan.Zero
        model.Paused <- Nullable false
        this.ResetCalc model
        model.Category <- Category.Values

    member this.ResetCalc model =
        model.LeftAndOperator <- ""
        model.Right <- "0"
        model.Computation <- id

    override this.Dispatcher = function
        | Digit value -> Sync(this.Digit value )
        | Operation symbol ->  Sync(this.Operation symbol)
        | Clear -> Sync this.Clear 
        | Backspace -> Sync this.Backspace 
        | CalculateResult -> Sync this.CalculateResult 
        | InputChanging(newValue, cancel) -> Sync(this.InputChanging(newValue, cancel))
        | InputChanged -> Sync this.InputChanged 
        | StopWatch -> Sync(ignore >> ticks.Pause)
        | StartWatch -> Sync(ignore >> ticks.Start)
        | RestartWatch -> Sync this.RestartWatch
        | CelsiusToFahrenheit -> Async this.CelsiusToFahrenheit
        | FahrenheitToCelsius -> Async this.FahrenheitToCelsius

    member this.Digit value model = 
        model.ClearAllErrors()
        model.Right <- model.Right + value

    member this.Operation symbol model =
        model.ClearAllErrors()
        match Operators.TryGetValue symbol with
        | true, op -> 
            model.LeftAndOperator <- model.Right + symbol;
            model.Computation <- op model.RightValue
            model.Right <- "0"
        | false, _ -> 
            invalidArg "operation symbol" symbol

    member this.Clear model =
        model.ClearAllErrors()
        this.ResetCalc model

    member this.Backspace model =
        model.ClearAllErrors()
        model.Right <- model.Right.Substring(0, model.Right.Length - 1)

    member this.CalculateResult model =
        model.ClearAllErrors()
        if model.LeftAndOperator.EndsWith("/") && model.Right = "0" then
            model |> Validation.setError <@ fun m -> m.Right @> "Cannot divide by zero"
        else
            model.Right <- model.RightValue |> model.Computation  |> string
            model.LeftAndOperator <- "";
    
    member this.InputChanging(newValue, cancel) model =
        model.ClearAllErrors()
        match Int32.TryParse newValue with 
        | false, _  ->  cancel()
        | _ -> ()

    member this.InputChanged model = 
        if model.Right <> "0" && model.Right.StartsWith("0") then 
            model.Right <- model.Right.Substring 1

    member this.RestartWatch model =
        ticks.Restart()
        model.Paused <- Nullable false

    member this.CelsiusToFahrenheit model = 
        async {
            let context = SynchronizationContext.Current
            assert (context <> null)
            use service = new TempConvertSoapClient(endpointConfigurationName = "TempConvertSoap")
            let! fahrenheit = 
                Async.FromBeginEnd(
                    string model.Celsius,
                    service.BeginCelsiusToFahrenheit, 
                    service.EndCelsiusToFahrenheit
                )
            do! Async.SwitchToContext context
            model.Fahrenheit <- decimal fahrenheit
        }

    member this.FahrenheitToCelsius model = 
        async {
            let context = SynchronizationContext.Current
            assert (context <> null)
            use service = new TempConvertSoapClient(endpointConfigurationName = "TempConvertSoap")
            let! celsius = 
                Async.FromBeginEnd(
                    string model.Fahrenheit,
                    service.BeginFahrenheitToCelsius, 
                    service.EndFahrenheitToCelsius
                )
            do! Async.SwitchToContext context
            model.Celsius <- decimal celsius
        }
